package es.chaveweb.poZit.client.ui.widget;

import java.util.ArrayList;
import java.util.List;

import com.google.gwt.cell.client.FieldUpdater;
import com.google.gwt.cell.client.ValueUpdater;
import com.google.gwt.core.client.GWT;
import com.google.gwt.dom.client.DivElement;
import com.google.gwt.dom.client.Element;
import com.google.gwt.dom.client.HeadingElement;
import com.google.gwt.dom.client.NativeEvent;
import com.google.gwt.dom.client.Style.Unit;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.MouseUpEvent;
import com.google.gwt.event.logical.shared.ResizeEvent;
import com.google.gwt.event.logical.shared.ResizeHandler;
import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.resources.client.ClientBundle;
import com.google.gwt.resources.client.CssResource;
import com.google.gwt.resources.client.CssResource.Import;
import com.google.gwt.resources.client.CssResource.ImportedWithPrefix;
import com.google.gwt.resources.client.ImageResource;
import com.google.gwt.resources.client.ImageResource.ImageOptions;
import com.google.gwt.resources.client.ImageResource.RepeatStyle;
import com.google.gwt.uibinder.client.UiBinder;
import com.google.gwt.uibinder.client.UiField;
import com.google.gwt.uibinder.client.UiHandler;
import com.google.gwt.user.cellview.client.DragCellList;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.Timer;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.FocusPanel;
import com.google.gwt.user.client.ui.HasText;
import com.google.gwt.user.client.ui.Image;
import com.google.gwt.user.client.ui.IsWidget;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.Widget;
import com.google.gwt.view.client.ListDataProvider;
import com.grupit.engine.util.Format;
import com.grupit.engine.util.FormatterLogger;

import es.chaveweb.poZit.client.mvp.widget.PoZitBoardPanel;
import es.chaveweb.poZit.client.mvp.widget.PoZitData;
import es.chaveweb.poZit.client.ui.Appearance;
import es.chaveweb.poZit.client.ui.cell.PoZitCell;
import es.chaveweb.poZit.client.ui.cell.PoZitCell.Color;
import es.chaveweb.poZit.client.ui.cell.PoZitCell.PoZitAppearance.PoZitCss;
import es.chaveweb.poZit.client.ui.widget.PoZitBoardPanelImpl.PoZitBoardPanelAppearance.PoZitBoardPanelResources;

public class PoZitBoardPanelImpl extends Composite implements PoZitBoardPanel, ResizeHandler {

	private static FormatterLogger logger = FormatterLogger.getLogger(PoZitBoardPanelImpl.class);

	private static PoZitBoardPanelUiBinder uiBinder = GWT.create(PoZitBoardPanelUiBinder.class);

	interface PoZitBoardPanelUiBinder extends UiBinder<Widget, PoZitBoardPanelImpl> {
	}

	public static class PoZitBoardPanelAppearance extends Appearance<PoZitBoardPanelResources> {
		public PoZitBoardPanelAppearance(PoZitBoardPanelResources resources) {
			super(resources);
		}

		public static interface PoZitBoardPanelResources extends ClientBundle {
			@Source({ PoZitBoardPanelCss.DEFAULT_CSS })
			@Import(PoZitCss.class)
			PoZitBoardPanelCss poZitBoardPanel();

			@Source("pinboardbackground.png")
			@ImageOptions(repeatStyle = RepeatStyle.Both)
			ImageResource pinBoardBackground();

			@Source("strip.png")
			ImageResource strip();

			@Source("leftTopFrame.png")
			ImageResource leftTopFrameImg();

			@Source("topFrame.png")
			@ImageOptions(repeatStyle = RepeatStyle.Horizontal)
			ImageResource topFrameImg();

			@Source("rightTopFrame.png")
			ImageResource rightTopFrameImg();

			@Source("rightFrame.png")
			@ImageOptions(repeatStyle = RepeatStyle.Vertical)
			ImageResource rightFrameImg();

			@Source("rightBottomFrame.png")
			ImageResource rightBottomFrameImg();

			@Source("bottomFrame.png")
			@ImageOptions(repeatStyle = RepeatStyle.Horizontal)
			ImageResource bottomFrameImg();

			@Source("leftBottomFrame.png")
			ImageResource leftBottomFrameImg();

			@Source("leftFrame.png")
			@ImageOptions(repeatStyle = RepeatStyle.Vertical)
			ImageResource leftFrameImg();

			@Source("medium-ajax-loader.gif")
			ImageResource loading();

			@Source("empty-bin.png")
			ImageResource emptyBin();

			@Source("full-bin.png")
			ImageResource fullBin();

			@Source("empty-bin-hover.png")
			ImageResource emptyBinHover();

			@Source("full-bin-hover.png")
			ImageResource fullBinHover();

			@Source("undo-icon.png")
			ImageResource undo();

			@Source("downArrow.png")
			ImageResource downArrowImg();

			@Source("upArrow.png")
			ImageResource upArrowImg();

			@Source("leftArrow.png")
			ImageResource leftArrowImg();

			@Source("rightArrow.png")
			ImageResource rightArrowImg();

			@Source("downArrow-hover.png")
			ImageResource downArrowHoverImg();

			@Source("upArrow-hover.png")
			ImageResource upArrowHoverImg();

			@Source("leftArrow-hover.png")
			ImageResource leftArrowHoverImg();

			@Source("rightArrow-hover.png")
			ImageResource rightArrowHoverImg();
		}

		@ImportedWithPrefix("noteBoardPanel")
		public interface PoZitBoardPanelCss extends CssResource {
			String DEFAULT_CSS = "PoZitBoardPanelImpl.css";

			String board();

			String boardList();

			String heading();

			String leftTopFrame();

			String topFrame();

			String rightTopFrame();

			String rightFrame();

			String rightBottomFrame();

			String bottomFrame();

			String leftBottomFrame();

			String leftFrame();

			String emptyText();

			String dragging();

			String loadingImg();

			String disappear();

			String bin();

			String binActive();

			String emptyBin();

			String fullBin();

			String undoButton();

			String btnLeftArrow();

			String btnRightArrow();

			String btnUpArrow();

			String btnDownArrow();

			String leftArrow();

			String rightArrow();

			String upArrow();

			String downArrow();

			String leftArrowHalo();

			String rightArrowHalo();

			String upArrowHalo();

			String downArrowHalo();
		}

		@Override
		public void ensureCssResourcesInjected() {
			this.resources.poZitBoardPanel().ensureInjected();
		}
	}

	@UiField
	HeadingElement heading;

	@UiField
	Image loadingImg;

	@UiField
	DivElement board;

	@UiField
	FocusPanel bin;

	@UiField
	Button btnUndo;

	@UiField
	Button btnLeft;

	@UiField
	Button btnRight;

	@UiField
	Button btnUp;

	@UiField
	Button btnDown;

	@UiField(provided = true)
	DragCellList<PoZitData> boardList;

	@UiField(provided = true)
	PoZitBoardPanelResources theme;

	private ListDataProvider<PoZitData> boardListData;

	private PoZitCell cell;

	private Presenter presenter = null;

	private boolean loading;

	private HandlerRegistration resizeHandler;

	public PoZitBoardPanelImpl() {
		PoZitBoardPanelAppearance appearance = new PoZitBoardPanelAppearance(
				GWT.<PoZitBoardPanelResources> create(PoZitBoardPanelResources.class));
		this.theme = appearance.getResources();

		this.cell = new PoZitCell(Color.YELLOW, new FieldUpdater<PoZitData, String>() {

			@Override
			public void update(int index, PoZitData object, String value) {
				assert (presenter != null) : Format.substitute(
						"El widget '{0}' necesita tener un presenter asociado. ¿Se te olvidó asignarlo?", this
								.getClass().getName());

				object.setNoteText(value);
				presenter.changeNoteText(object.getId(), value);
				PoZitBoardPanelImpl.this.getElement().scrollIntoView();
			}
		}) {
			private int offsetX;
			private int offsetY;

			@Override
			public void onMouseDown(Context context, Element parent, PoZitData value, NativeEvent event,
					ValueUpdater<PoZitData> valueUpdater) {
				//TODO: Arrastrar Dnd HTML5
				if (Element.is(event.getEventTarget())) {
					Element containerElm = parent.getFirstChildElement();
					Element contentElm = Element
							.as(containerElm.getChild(Template.PoZitElementIndex.CONTENT.getIndex()));
					Element targetElm = Element.as(event.getEventTarget());

					if (!targetElm.equals(contentElm)) {
						containerElm.addClassName(theme.poZitBoardPanel().dragging());
						this.offsetX = -PoZitBoardPanelImpl.this.getRelativeX(event, containerElm);
						this.offsetY = -PoZitBoardPanelImpl.this.getRelativeY(event, containerElm);
						boardList.startDrag(context, parent, value);
						DOM.eventPreventDefault(DOM.eventGetCurrentEvent());
					}
				}
			}

			@Override
			public void onMouseMove(Context context, Element parent, PoZitData value, NativeEvent event,
					ValueUpdater<PoZitData> valueUpdater) {
				if (boardList.isDragging(value)) {
					int x = PoZitBoardPanelImpl.this.getRelativeX(event,
							PoZitBoardPanelImpl.this.boardList.getElement());
					int y = PoZitBoardPanelImpl.this.getRelativeY(event,
							PoZitBoardPanelImpl.this.boardList.getElement());

					parent.getFirstChildElement().getStyle().setLeft(x + offsetX, Unit.PX);
					parent.getFirstChildElement().getStyle().setTop(y + offsetY, Unit.PX);
				}
			}

			@Override
			public void onMouseUp(Context context, Element parent, PoZitData value, NativeEvent event,
					ValueUpdater<PoZitData> valueUpdater) {
				if (boardList.isDragging(value)) {
					boardList.endDrag();
					Element containerElm = parent.getFirstChildElement();
					containerElm.removeClassName(theme.poZitBoardPanel().dragging());

					int xInBoard = PoZitBoardPanelImpl.this.getRelativeX(event,
							PoZitBoardPanelImpl.this.boardList.getElement())
							+ offsetX;
					int yInBoard = PoZitBoardPanelImpl.this.getRelativeY(event,
							PoZitBoardPanelImpl.this.boardList.getElement())
							+ offsetY;

					if (xInBoard != value.getX() || yInBoard != value.getY()) {
						value.setX(xInBoard);
						value.setY(yInBoard);

						assert (presenter != null) : Format.substitute(
								"El widget '{0}' necesita tener un presenter asociado. ¿Se te olvidó asignarlo?", this
										.getClass().getName());

						if (xInBoard > cell.getMaxX()) {
							cell.setMaxX(xInBoard);
						}

						if (yInBoard > cell.getMaxY()) {
							cell.setMaxY(yInBoard);
						}
						refreshScrollButtonsVisibility.run();

						presenter.changeNoteCoord(value.getId(), xInBoard, yInBoard);
					}
				}
			}
		};
		boardList = new DragCellList<PoZitData>(cell);
		boardListData = new ListDataProvider<PoZitData>(new ArrayList<PoZitData>());
		boardListData.addDataDisplay(boardList);

		Widget emptyListWidget = new Label("No tienes ninguna nota.");
		emptyListWidget.setStyleName(theme.poZitBoardPanel().emptyText());
		boardList.setEmptyListWidget(emptyListWidget);

		initWidget(uiBinder.createAndBindUi(this));

	}

	@Override
	protected void onLoad() {
		super.onLoad();
		refreshScrollButtonsVisibility.run();
		this.resizeHandler = Window.addResizeHandler(this);
	}

	@Override
	protected void onUnload() {
		if (this.resizeHandler != null) {
			this.resizeHandler.removeHandler();
		}
		super.onUnload();
	}

	protected Timer refreshScrollButtonsVisibility = new Timer() {

		public void run() {
			btnUp.setEnabled(true);
			btnLeft.setEnabled(true);
			btnDown.setEnabled(true);
			btnRight.setEnabled(true);

			//TODO: Aún no calcula bien el alto
			
			int left = boardList.getElement().getOffsetLeft() + board.getOffsetLeft();
			int top = boardList.getElement().getOffsetTop() + board.getOffsetTop();
			int width = board.getOffsetWidth();
			int height = board.getOffsetHeight();
			int maxX = -left + width - (btnRight.getOffsetWidth() * 2);
			int maxY = -top + height - (btnDown.getOffsetHeight() * 2);

			boolean leftVisibility = !loading && (left < 0);
			boolean topVisibility = !loading && (top < 0);
			boolean bottomVisibility = !loading && (cell.getMaxY() > maxY);
			boolean rightVisibility = !loading && (cell.getMaxX() > maxX);

			logger.debug("left={0}; top={1}; width={2}; height={3}", left, top, width, height);
			logger.debug("cell.maxX={0}; maxX={1}; cell.maxY={2}; maxY={3}", cell.getMaxX(), maxX, cell.getMaxY(), maxY);

			btnUp.setVisible(topVisibility);
			btnLeft.setVisible(leftVisibility);
			btnDown.setVisible(bottomVisibility);
			btnRight.setVisible(rightVisibility);
		}
	};

	@UiHandler("bin")
	public void onBinMouseUp(MouseUpEvent evt) {
		assert (presenter != null) : Format.substitute(
				"El widget '{0}' necesita tener un presenter asociado. ¿Se te olvidó asignarlo?", this.getClass()
						.getName());

		if (boardList.isDragging()) {
			bin.addStyleName(theme.poZitBoardPanel().binActive());
			logger.info("Papelera activa");

			final PoZitData toDelete = boardList.endDrag();

			presenter.sendToBin(toDelete, new Presenter.ConfirmResponse() {

				@Override
				public void onUserResponse(boolean confirmed) {
					if (!confirmed) {
						boardListData.getList().remove(toDelete);
						boardListData.getList().add(toDelete);
					}

					bin.removeStyleName(theme.poZitBoardPanel().binActive());
				}
			});
		}
	}

	@UiHandler("bin")
	public void onBinClicked(ClickEvent evt) {
		assert (presenter != null) : Format.substitute(
				"El widget '{0}' necesita tener un presenter asociado. ¿Se te olvidó asignarlo?", this.getClass()
						.getName());

		presenter.clearBin();
	}

	@UiHandler("btnUndo")
	public void onButtonUndoClicked(ClickEvent evt) {
		assert (presenter != null) : Format.substitute(
				"El widget '{0}' necesita tener un presenter asociado. ¿Se te olvidó asignarlo?", this.getClass()
						.getName());

		presenter.undoLastSendToBin();
	}

	@UiHandler("btnUp")
	public void onBtnUpClicked(ClickEvent evt) {
		btnUp.setEnabled(false);
		int offsetY = (int) (boardList.getElement().getOffsetTop() + (board.getOffsetHeight() * 0.75));
		if (offsetY > 0) {
			offsetY = 0;
			btnUp.setVisible(false);
		}
		boardList.getElement().getStyle().setTop(offsetY, Unit.PX);
		refreshScrollButtonsVisibility.schedule(1000);
	}

	@UiHandler("btnLeft")
	public void onBtnLeftClicked(ClickEvent evt) {
		btnLeft.setEnabled(false);
		int offsetX = (int) (boardList.getElement().getOffsetLeft() + (board.getOffsetWidth() * 0.75));
		if (offsetX > 0) {
			offsetX = 0;
		}
		boardList.getElement().getStyle().setLeft(offsetX, Unit.PX);
		refreshScrollButtonsVisibility.schedule(1000);
	}

	@UiHandler("btnDown")
	public void onBtnDownClicked(ClickEvent evt) {
		btnDown.setEnabled(false);
		int offsetY = (int) (boardList.getElement().getOffsetTop() - (board.getOffsetHeight() * 0.75));
		int addHeight = (int) (boardList.getElement().getOffsetHeight() + (board.getOffsetHeight() * 0.75));
		boardList.getElement().getStyle().setTop(offsetY, Unit.PX);
		boardList.getElement().getStyle().setHeight(addHeight, Unit.PX);
		refreshScrollButtonsVisibility.schedule(1000);
	}

	@UiHandler("btnRight")
	public void onBtnRightClicked(ClickEvent evt) {
		btnRight.setEnabled(false);
		int offsetX = (int) (boardList.getElement().getOffsetLeft() - (board.getOffsetWidth() * 0.75));
		int addWidth = (int) (boardList.getElement().getOffsetWidth() + (board.getOffsetWidth() * 0.75));
		boardList.getElement().getStyle().setLeft(offsetX, Unit.PX);
		boardList.getElement().getStyle().setWidth(addWidth, Unit.PX);
		refreshScrollButtonsVisibility.schedule(1000);
	}

	public void setHeading(String heading) {
		this.heading.setInnerText(heading);
	}

	public String getHeding() {
		return this.heading.getInnerText();
	}

	public IsWidget boardListWidget() {
		return boardList;
	}

	public void setEmptyListMessage(String message) {
		((HasText) boardList.getEmptyListWidget()).setText(message);
	}

	public void setPoZitColor(Color color) {
		this.cell.setColor(color);
	}

	@Override
	public void setPresenter(Presenter presenter) {
		this.presenter = presenter;
	}

	@Override
	public List<PoZitData> notes() {
		return this.boardListData.getList();
	}

	public Element headingElement() {
		return heading;
	}

	protected int getRelativeX(NativeEvent e, Element target) {
		return e.getClientX() - target.getAbsoluteLeft() + target.getScrollLeft()
				+ target.getOwnerDocument().getScrollLeft();
	}

	/**
	 * Gets the mouse y-position relative to a given element.
	 * 
	 * @param target
	 *            the element whose coordinate system is to be used
	 * @return the relative y-position
	 */
	protected int getRelativeY(NativeEvent e, Element target) {
		return e.getClientY() - target.getAbsoluteTop() + target.getScrollTop()
				+ target.getOwnerDocument().getScrollTop();
	}

	@Override
	public void setLoading(boolean loading) {
		this.loading = loading;
		if (loading) {
			boardList.addStyleName(this.theme.poZitBoardPanel().disappear());
			bin.addStyleName(this.theme.poZitBoardPanel().disappear());
			btnUndo.addStyleName(this.theme.poZitBoardPanel().disappear());
			loadingImg.removeStyleName(this.theme.poZitBoardPanel().disappear());

			boardList.getElement().getStyle().setTop(0, Unit.PX);
			boardList.getElement().getStyle().setLeft(0, Unit.PX);

		} else {
			boardList.removeStyleName(this.theme.poZitBoardPanel().disappear());
			bin.removeStyleName(this.theme.poZitBoardPanel().disappear());
			btnUndo.removeStyleName(this.theme.poZitBoardPanel().disappear());
			loadingImg.addStyleName(this.theme.poZitBoardPanel().disappear());
		}

		refreshScrollButtonsVisibility.schedule(1000);
	}

	@Override
	public boolean isLoading() {
		return this.loading;
	}

	@Override
	public void setUndoActive(boolean active) {
		btnUndo.setVisible(active);
		if (active) {
			bin.removeStyleName(this.theme.poZitBoardPanel().emptyBin());
			bin.addStyleName(this.theme.poZitBoardPanel().fullBin());
			bin.setTitle("Vacía tu papelera haciendo clic");
		} else {
			bin.removeStyleName(this.theme.poZitBoardPanel().fullBin());
			bin.addStyleName(this.theme.poZitBoardPanel().emptyBin());
			bin.setTitle("Elimina una nota lanzándola a la papelera");
		}
	}

	@Override
	public boolean isUndoActive() {
		return btnUndo.isVisible();
	}

	@Override
	public void onResize(ResizeEvent event) {
		int blY = boardList.getElement().getOffsetTop() + boardList.getElement().getOffsetHeight();
		int bY = board.getOffsetHeight();

		if (blY < bY) {
			int diff = bY - blY;
			int newHeight = boardList.getElement().getOffsetHeight() + diff;
			boardList.getElement().getStyle().setHeight(newHeight, Unit.PX);
		}

		refreshScrollButtonsVisibility.run();
	}
}
